package sma.agents;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.swing.JComponent;
import javax.vecmath.Vector2d;

import ihm.AppliWindow;

import org.janusproject.kernel.address.Address;
import org.janusproject.kernel.agentsignal.QueuedSignalAdapter;
import org.janusproject.kernel.channels.Channel;
import org.janusproject.kernel.channels.ChannelInteractable;
import org.janusproject.kernel.crio.core.GroupAddress;
import org.janusproject.kernel.status.Status;
import org.janusproject.kernel.status.StatusFactory;

import sma.agents.bodies.ObstacleAgentBody;
import sma.agents.channels.GoalChannel;
import sma.agents.channels.ObstacleChannel;
import sma.agents.listeners.MoveGoalListener;
import sma.agents.listeners.MoveObstacleListener;
import sma.environment.LocalEnvironment;
import sma.organizations.VirtualEnvironmentOrga;
import sma.organizations.roles.Obstacle;
import sma.organizations.signals.SendCreateObstacleSignal;
import sma.organizations.signals.SendDeleteRepulsiveSignal;
import sma.organizations.signals.SendMoveObstacleSignal;
import appli.utils.Config;
import appli.utils.MoveEnum;

/**
 * PositiveAgent : ce sont eux qui ont pour role de prendre possession du
 * terrain afin de se diriger vers l'objectif tout en evitant les obstacles.
 * C'est selon leur comportement que l'on pourra determiner le GravityAgent. Ils
 * appartiennent au groupe Voiture et au groupe Environment.
 * 
 * Les PositiveAgent peuvent appartenir à différents groupes, chaque groupe 
 * correspondant à un télémètre (à ne pas confondre avec les groupes d'un SMA).
 * 
 * @author Etienne
 * @since 0.1
 * @version 0.2
 */
public class ObstacleAgent extends StandartAgent implements ChannelInteractable, MoveObstacleListener, MoveGoalListener {
	private final ObstacleChannelImpl obstacleImpl = new ObstacleChannelImpl();
	
	/**
	 * Create a local listener on the CreateObstacleSignals coming from the agent itself.
	 */
	private QueuedSignalAdapter<SendCreateObstacleSignal> privateCreateSignals = new QueuedSignalAdapter<SendCreateObstacleSignal>(SendCreateObstacleSignal.class);
	
	/**
	 * Create a local listener on deleteObstaclesignals coming from the agent itself.
	 * 
	 */
	private QueuedSignalAdapter<SendDeleteRepulsiveSignal> privateDeleteSignals = new QueuedSignalAdapter<SendDeleteRepulsiveSignal>(SendDeleteRepulsiveSignal.class);
	
	/**
	 * Identifiant de l'agent obstacle
	 */
	private int id;
	
	/**
	 * La gui, afin de communiquer avec (via des signaux)
	 */
	private AppliWindow gui;
	
	/**
	 * Est ce qu'il est dans des coordonnées polaire sou cartésiennes ?
	 */
	private Boolean polaire;
	
	/**
	 * Dans quelle direction regarde le télémètre ?
	 */
	private int rotation = 0;
	
	/**
	 * La liste des agents bouche trou... à rechecker il y a des soucis.
	 */
	private List<RepulsiveAgent> agentsBoucheTrou = new ArrayList<RepulsiveAgent>();

	/**
	 * Constructeur. L'agent evolue maintenant dans un environnement.
	 * 
	 * @param _environment : environnement local
	 * @param gui AppliWindowd
	 * @param _id l'identifiant de l'agent
	 * @param _groupe le groupe de l'obstacle
	 * @param _rotation la rotation à appliquer aux agents selon la direction du télémètre
	 * @see LocalEnvironment
	 */
	public ObstacleAgent(sma.environment.LocalEnvironment _environment, AppliWindow gui, int _id, int _groupe, int _rotation) {
		// Son environnement :
		this.environment = _environment;

		// Son id :
		this.id = _id;
		
		//Son groupe :
		this.groupe = _groupe;
		
		//Sa direction
		this.rotation = _rotation;

		// On lui affecte un body propre à son role :
		this.body = new ObstacleAgentBody();
		
		this.gui = gui;

		if (Config.DEBUG == true)
			System.out.println("Agent Obstacle " + this.getName()+ " operationnel");
		
		obstacleImpl.addMoveObstacleListener(this);
	}

	/**
	 * activate, Permet d'activer l'agent avant qu'il vive. Il prend pour role
	 * ObstacleAgent dans le groupe Obstacle et NegativeAgent dans le groupe
	 * Environment. Par ailleurs des paramtres de sortie s'afficheront en cas de
	 * mode debug.
	 * 
	 * @param  parameters inutile de mettre quelque chose ici
	 */
	@Override
	public Status activate(Object... parameters) {
		super.init((JComponent) parameters[0]);
		//gui = (AppliWindow) parameters[0];
		polaire = (Boolean) parameters[1];
		if (Config.DEBUG == true)
			System.out.println("Agent Obstacle " + this.getName() + " activé");
		
		// Recuperation de l'adresse du groupe Obstacles
		GroupAddress virtualEnvirment = getOrCreateGroup(VirtualEnvironmentOrga.class);
		
		// Affectation du role de Obstacle dans le groupe virtualEnvironement
		if (requestRole(Obstacle.class, virtualEnvirment, this.id, this.body, (Boolean) parameters[1], this.groupe, this.rotation)) {
			if (Config.DEBUG == true)
				System.out.println("ObstacleAgent appartient au role Obstacle dans le groupe virtualEnvirment");
			getSignalEmitter().addSignalListener(this.privateCreateSignals);
			getSignalEmitter().addSignalListener(this.privateDeleteSignals);
			return StatusFactory.ok(this);
		} else 
			if (Config.DEBUG == true)
				System.out.println("L'agent Obstacle ne peut pas appartenir au role Obstacle dans le groupe virtualEnvirment...");
			
		
		return StatusFactory.cancel(this);
	}
	
	/**
	 * Vie de l'agent :
	 */
	@Override
	public Status live() {
		super.live();
		if(polaire) {
			if(Config.FILL_OBSTACLE_HOLE){
				//Supprime les agents bouchetrou si on en recoit le signal
				//ATTENTION : a faire avant de creer les bouches trou... 
				SendDeleteRepulsiveSignal sig;
				sig = this.privateDeleteSignals.getFirstAvailableSignal();
				
				while (sig != null) {
					//Destruction des agents bouchetrou
					deleteAllBoucheTrou();
					
					sig = this.privateDeleteSignals.getFirstAvailableSignal();
				}
				
				//Est ce que je dois compléter les trous ?
				SendCreateObstacleSignal sig2;
				sig2 = this.privateCreateSignals.getFirstAvailableSignal();
				int i = 0;
				while (sig2 != null) {
					Vector2d newPosition = sig2.getPosition();
					
					//Créons l'agent : 
					RepulsiveAgent tmpAgent = new RepulsiveAgent(this.environment, (this.id*1000)+i, this.groupe);
					tmpAgent.getBody().setPosition(newPosition);
					tmpAgent.getBody().setColor(Color.ORANGE);
					
					agentsBoucheTrou.add(tmpAgent);
					this.launchLightAgent(tmpAgent, "BoucheTrouAgent_" + (this.id*1000)+i, gui.getMassCenterPanel(), false);				
					
	
					//Agent suivant :
					i++;
					sig2 = this.privateCreateSignals.getFirstAvailableSignal();
				}
			}
		}
		
		return StatusFactory.ok(this);
	}

	private void deleteAllBoucheTrou() {
		
		for(RepulsiveAgent agent : agentsBoucheTrou){
			this.kill(agent.getAddress());
		}
		
		agentsBoucheTrou = new ArrayList<RepulsiveAgent>();
		
		
	}

	/**
	 * Mort de l'agent
	 */
	@Override
	public Status end() {
		if (Config.DEBUG == true)
			print("Je suis mort....");
		return StatusFactory.ok(this);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <C extends Channel> C getChannel(Class<C> channelClass, Object... params) {
		if (channelClass.isAssignableFrom(GoalChannel.class)) {
			return channelClass.cast(this.obstacleImpl);
		}
		return null;
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public Set<? extends Class<? extends Channel>> getSupportedChannels() {
		return Collections.singleton(GoalChannel.class);
	}

	private class ObstacleChannelImpl implements ObstacleChannel {
		@Override
		public Address getChannelOwner() {
			return getAddress();
		}

		@Override
		public void addMoveObstacleListener(MoveGoalListener listener) {
			ObstacleAgent.this.addEventListener(MoveGoalListener.class, listener);
		}

		@Override
		public void removeMoveObstacleListener(MoveGoalListener listener) {
			ObstacleAgent.this.removeEventListener(MoveGoalListener.class, listener);
		}

		@Override
		public void moveObstacle(int _position, int lastObstaclePosition) {
			fireSignal(new SendMoveObstacleSignal(this, _position, lastObstaclePosition));
		}
	}

	@Override
	public void onMoveObstacle(int position) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onMoveGoal(MoveEnum move) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * @return the obstacleImpl
	 */
	public ObstacleChannelImpl getObstacleImpl() {
		return obstacleImpl;
	}
}